package jmine.tec.web.wicket.component.menu.cfg;

import static jmine.tec.web.wicket.pages.main.ExternalPage.SOURCE_URL;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jmine.tec.web.wicket.component.menu.MenuInternalLink;
import jmine.tec.web.wicket.component.menu.MenuItem;
import jmine.tec.web.wicket.component.menu.MenuJSFLink;
import jmine.tec.web.wicket.component.menu.MenuLink;
import jmine.tec.web.wicket.pages.main.ExternalPage;
import jmine.tec.web.wicket.security.SecureSession;

import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.wicket.Component;
import org.apache.wicket.Page;
import org.apache.wicket.PageParameters;
import org.apache.wicket.RequestCycle;

import bancosys.tec.security.authorization.UrlPermission;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import com.thoughtworks.xstream.annotations.XStreamOmitField;

/**
 * Item de uma configuração de menu.
 * 
 * @author lundberg
 */
@XStreamAlias("item")
public class MenuConfigItem implements Comparable<MenuConfigItem>, Serializable {

    @XStreamAsAttribute
    private String label;

    @XStreamImplicit
    private List<MenuConfigItem> children;

    @XStreamAsAttribute
    private String action;

    @XStreamOmitField
    private Class<?> page;

    @XStreamAsAttribute
    private Integer order;

    /**
     * @return the label
     */
    public String getLabel() {
        return this.label;
    }

    /**
     * @param label the label to set
     */
    public void setLabel(String label) {
        this.label = label;
    }

    /**
     * @return the children
     */
    public List<MenuConfigItem> getChildren() {
        if (this.children == null) {
            this.children = new ArrayList<MenuConfigItem>();
        }
        return this.children;
    }

    /**
     * @param children the children to set
     */
    public void setChildren(List<MenuConfigItem> children) {
        this.children = children;
    }

    /**
     * @return the action
     */
    public String getAction() {
        return this.action;
    }

    /**
     * @param action the action to set
     */
    public void setAction(String action) {
        this.action = action;
    }

    /**
     * @return the page
     */
    public Class<?> getPage() {
        return this.page;
    }

    /**
     * @param page the page to set
     */
    public void setPage(Class<?> page) {
        this.page = page;
    }

    /**
     * @return the order
     */
    public Integer getOrder() {
        return this.order;
    }

    /**
     * @param order the order to set
     */
    public void setOrder(Integer order) {
        this.order = order;
    }

    /**
     * Verifica se este item possui filhos.
     * 
     * @return boolean
     */
    public boolean hasChildren() {
        return !this.getChildren().isEmpty();
    }

    /**
     * Verifica se este item define página a ser apontada.
     * 
     * @return boolean
     */
    public boolean hasPage() {
        return this.page != null;
    }

    /**
     * Funde este item de configuração com outro.
     * 
     * @param configItem configItem
     * @return self
     */
    public MenuConfigItem merge(MenuConfigItem configItem) {
        if (configItem.getOrder() != null) {
            this.setOrder(configItem.getOrder());
        }
        if (configItem.hasChildren()) {
            Map<String, MenuConfigItem> childMap = new HashMap<String, MenuConfigItem>();
            for (MenuConfigItem child : this.getChildren()) {
                childMap.put(child.getLabel(), child);
            }
            for (MenuConfigItem submenu : configItem.getChildren()) {
                String key = submenu.getLabel();
                if (childMap.containsKey(key)) {
                    childMap.get(key).merge(submenu);
                } else {
                    this.getChildren().add(submenu);
                }
            }
            Collections.sort(this.getChildren());
        } else {
            this.setPage(configItem.getPage());
            this.setAction(configItem.getAction());
        }
        return this;
    }

    /**
     * {@inheritDoc}
     */
    public void sort() {
        Collections.sort(this.getChildren());
        for (MenuConfigItem child : this.getChildren()) {
            child.sort();
        }
    }

    /**
     * {@inheritDoc}
     */
    public int compareTo(MenuConfigItem o) {
        return new CompareToBuilder().append(this.order, o.order).append(this.label, o.label).toComparison();
    }

    /**
     * Adiciona uma página ao menu.
     * 
     * @param newPage page
     * @param path path
     * @return item criado
     */
    public MenuConfigItem addPage(Class<?> newPage, String... path) {
        return this.addPage(newPage, Arrays.asList(path));
    }

    /**
     * Adiciona uma página ao menu.
     * 
     * @param newPage page
     * @param path path
     * @return item criado
     */
    public MenuConfigItem addPage(Class<?> newPage, List<String> path) {
        if (path.isEmpty()) {
            this.page = newPage;
            return this;
        } else {
            return this.findOrCreateChild(path.get(0)).addPage(newPage, path.subList(1, path.size()));
        }
    }

    /**
     * Adds an empty menu or submenu
     * 
     * @param path path
     * @return created menu
     */
    public MenuConfigItem addMenu(String... path) {
        return this.addMenu(Arrays.asList(path));
    }

    /**
     * Adds an empty menu or submenu
     * 
     * @param path path
     * @return created menu
     */
    public MenuConfigItem addMenu(List<String> path) {
        if (path.isEmpty()) {
            return this;
        } else {
            return this.findOrCreateChild(path.get(0)).addMenu(path.subList(1, path.size()));
        }
    }

    /**
     * Busca o MenuConfigItem com o path
     * 
     * @param path path
     * @return MenuConfigItem
     */
    public MenuConfigItem findItem(String... path) {
        return this.findItem(Arrays.asList(path));
    }

    /**
     * Busca o MenuConfigItem com o path
     * 
     * @param path path
     * @return MenuConfigItem
     */
    public MenuConfigItem findItem(List<String> path) {
        if (path.isEmpty()) {
            return this;
        } else {
            return this.findChild(path.get(0)).findItem(path.subList(1, path.size()));
        }
    }

    /**
     * Busca ou cria o item com o label.
     * 
     * @param childLabel label
     * @return item
     */
    private MenuConfigItem findOrCreateChild(String childLabel) {
        MenuConfigItem item = this.findChild(childLabel);
        if (item == null) {
            item = new MenuConfigItem();
            item.setLabel(childLabel);
            this.getChildren().add(item);
        }
        return item;
    }

    /**
     * Procura o item filho com o label, null caso não encontrado.
     * 
     * @param childLabel childLabel
     * @return MenuConfigItem
     */
    private MenuConfigItem findChild(String childLabel) {
        for (MenuConfigItem item : this.getChildren()) {
            if (item.getLabel().equals(childLabel)) {
                return item;
            }
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return this.label;
    }

    /**
     * Cria o item de menu
     * 
     * @return MenuItem
     */
    public MenuItem createSubMenu() {
        MenuItem submenu = new MenuItem(this.getLabel());
        for (MenuConfigItem child : this.getChildren()) {
            if (child.hasChildren()) {
                submenu.addSubMenu(child.createSubMenu());
            } else {
                submenu.addLink(child.createLink());
            }
        }
        return submenu;
    }

    /**
     * Cria o link
     * 
     * @return menu link
     */
    protected MenuLink createLink() {
        if (this.hasPage()) {
            return this.createInternalLink();
        } else {
            return this.createJSFLink();
        }
    }

    /**
     * Busca a página wicket apontada pelo item de configuração do menu
     * 
     * @return menu link
     */
    protected MenuLink createInternalLink() {
        return new MenuInternalLink(this.getLabel(), this.getPage());
    }

    /**
     * Cria um link externo
     * 
     * @return menu link
     */
    protected MenuJSFLink createJSFLink() {
        return new MenuJSFLink(this.getLabel(), this.getAction());
    }

    /**
     * Cria a url do item de menu
     * 
     * @param parentPath parentPath
     * @return url
     */
    public String createURL(String parentPath) {
        // String context = ((WebRequestCycle) RequestCycle.get()).getWebRequest().getHttpServletRequest().getRequestURL().toString();
        if (this.hasChildren()) {
            return null;
        } else if (this.hasPage()) {
            return RequestCycle.get().urlFor(this.page.asSubclass(Page.class), null).toString();
        } else {
            PageParameters parameters = new PageParameters();
            MenuJSFLink link = this.createJSFLink();
            link.setPath(parentPath);
            parameters.add(SOURCE_URL, link.getDestinationURL());
            return RequestCycle.get().urlFor(ExternalPage.class, parameters).toString();
        }
    }

    /**
     * Verifica se o item deve ser visível.
     * 
     * @return boolean
     */
    public boolean isVisible() {
        if (this.hasChildren()) {
            for (MenuConfigItem child : this.children) {
                if (child.isVisible()) {
                    return true;
                }
            }
            return false;
        } else if (this.hasPage()) {
            return SecureSession.getSecureSession().getAuthorizationStrategy()
                    .isInstantiationAuthorized(this.getPage().asSubclass(Component.class));
        } else {
            return SecureSession.getSecureSession().getSecurityManager().getSecurityService()
                    .hasPermission(UrlPermission.class, this.getAction());
        }
    }

    /**
     * Removes a menu item on the given path
     * 
     * @param path path
     * @return MenuConfigItem
     */
    public MenuConfigItem remove(List<String> path) {
        if (path.size() == 1) {
            MenuConfigItem item = this.findItem(path);
            this.children.remove(item);
            return item;
        } else {
            return this.findItem(path.get(0)).remove(path.subList(1, path.size()));
        }
    }

    /**
     * Adds an item to the menu. Might trigger merge.
     * 
     * @param item item
     * @return MenuConfigItem
     */
    MenuConfigItem addItem(MenuConfigItem item) {
        return this.findOrCreateChild(item.getLabel()).merge(item);
    }
}
